<?php
namespace PsiFramework\Classes\DAO;

use PsiFramework\Classes\Util\Database\Query\SqlOrder;

import("Classes.Model.Object");
import("Classes.DAO.DAOInterface.IDAO");
import("Classes.Util.Database.Builder.QueryBuilder");
import("Classes.Util.Database.Filters.QueryFilter");
import("Classes.Util.Database.Filters.Paginator");
import("Classes.Util.Database.Query.SqlColumn");
import("Classes.Util.Database.Query.SqlTable");
import("Classes.Util.Database.Query.SqlOperation");
import("Classes.Util.Database.Query.SqlJoin");
import("Classes.Util.Database.Query.SqlOn");
import("Classes.Util.Database.Query.SqlUsing");
import("Classes.Util.Database.Query.SqlAlterTableQuery");
import("Classes.Util.Parameters.ObjectParameters");
import("Classes.Util.Log.Logger");
import("Classes.Util.Object.Json");

//Import after class to prevent AsbtractDAO heritage failures
importAfter("Classes.Util.Database.Database");
importAfter("Classes.Util.Parameters.DAO.ObjectParametersDAO");
importAfter("Classes.Util.Manager.ParametersManager");
importAfter("Classes.Util.Manager.ObjectManager");
importAfter("Classes.Model.ObjectProxy");
importAfter("Classes.Factory.DAOFactory");
importAfter("Classes.Factory.ObjectFactory");

use \PsiFramework\Classes\DAO\DAOInterface\IDAO;
use \PsiFramework\Classes\Exceptions\SecurityException;
use \PsiFramework\Classes\Factory\DAOFactory;
use \PsiFramework\Classes\Factory\ObjectFactory;
use \PsiFramework\Classes\Model\Object;
use \PsiFramework\Classes\Model\ObjectProxy;
use \PsiFramework\Classes\Util\Database\Database;
use \PsiFramework\Classes\Util\Database\Builder\QueryBuilder;
use \PsiFramework\Classes\Util\Database\Filters\QueryFilter;
use \PsiFramework\Classes\Util\Database\Filters\Paginator;
use \PsiFramework\Classes\Util\Database\Query\SqlAddColumn;
use \PsiFramework\Classes\Util\Database\Query\SqlColumn;
use \PsiFramework\Classes\Util\Database\Query\SqlTable;
use \PsiFramework\Classes\Util\Database\Query\SqlFunction;
use \PsiFramework\Classes\Util\Database\Query\SqlOperation;
use \PsiFramework\Classes\Util\Database\Query\SqlJoin;
use \PsiFramework\Classes\Util\Database\Query\SqlOn;
use \PsiFramework\Classes\Util\Database\Query\SqlUsing;
use \PsiFramework\Classes\Util\Database\Query\SqlValue;
use \PsiFramework\Classes\Util\Database\Query\SqlAlterTableQuery;
use \PsiFramework\Classes\Util\Database\Query\SqlType;
use \PsiFramework\Classes\Util\Database\Query\SqlChangeColumnType;
use \PsiFramework\Classes\Util\Log\Logger;
use \PsiFramework\Classes\Util\Manager\ApplicationManager;
use \PsiFramework\Classes\Util\Manager\ParametersManager;
use \PsiFramework\Classes\Util\Manager\ObjectManager;
use \PsiFramework\Classes\Util\Object\Json;
use \PsiFramework\Classes\Util\Parameters\ObjectParameters;
use \PsiFramework\Classes\Util\Parameters\Object\AttributeParameters;
use \PsiFramework\Classes\Util\Parameters\DAO\ObjectParametersDAO;
use \PsiFramework\Classes\Util\Security\Manager\SecurityManager;
use PsiFramework\Classes\Util\UI\Printer;

/**
 * PsiFramework
 * Php Simple Framework
 *
 * @author Rémi San
 * @version beta
 */

/**
 * The AbstractDAO class is the Abstract Class for DAOs
 *
 * @package DAO
 */
abstract class AbstractDAO implements IDAO
{

    public static /*boolean*/     $storeObjects=true;
    
    protected /*string*/           $_alias;

    /**
     * 
     * @var Database
     */
    protected /*Database*/         $_database;
    
    /**
     * @var ObjectParameters
     */
    protected /*ObjectParameters*/ $_objectParameters ;
    
    /**
     * @var Logger
     */
    protected /*Logger*/           $_logger;

    protected /*array*/            $_objectsAlreadyRetrieved = array();

    protected function __construct(ObjectParameters $objectParameters=null)
    {

        $className = get_class($this);
        $this->_logger = new Logger($className);

        $this->_database = Database::getInstance();
        if (!is_null($objectParameters)) {
            $this->_alias = $objectParameters->getName();
        } else {
            $this->_alias = null ;
        }

        $this->_objectParameters = $objectParameters ;
    }

    /**
     * 
     */
    protected function onlyPk()
    {
        return (count($this->_objectParameters->getParameters()) == count($this->_objectParameters->getPrimaryKey()));
    }
    
    public function getEmptyObject()
    {
        $obj = ObjectFactory::getNewObject(
            $this->_objectParameters->getClass(),
            true
        );
        foreach ($this->_objectParameters->getParameters() as $param) {
            if (($param->isPrimitive() || $param->isPrimitiveObject())
                && !$param->canBeNull
            ) {
                $val = null ;
                switch($param->sqlType) {
                case SqlType::$varchar :
                case SqlType::$text :
                    $val = '' ;
                    break;
                case SqlType::$serial :
                case SqlType::$integer :
                case SqlType::$long :
                case SqlType::$double :
                case SqlType::$float :
                    $val = 0 ;
                    break;
                case SqlType::$boolean :
                    $val = false ;
                    break;
                case SqlType::$dateTime :
                    $val = new Date();
                    break;
                }
                if (!is_null($val)) {
                    $value = (is_null($val))?'null':$val ;
                    $this->_logger->debug(
                        $this->_objectParameters->getName() . ' : ' .
                        $param->name . ' -> ' . $value
                    );
                    $obj->{$param->setter}($val);
                }
            }
        }
        return $obj ;
    }

    public function getSearchService()
    {
        return $this->_objectParameters->getSearchService();
    }
    
    public function tableExists()
    {
        $table = new SqlTable(
            $this->_objectParameters->getTable(),
            $this->_objectParameters->getSchema()
        );
        return $this->_database->tableExists(
            $table,
            $this->_objectParameters->isView()
        );
    }

    public function updateTable()
    {
        $table = new SqlTable(
            $this->_objectParameters->getTable(),
            $this->_objectParameters->getSchema()
        );
        $tableStructure = $this->_database->getTableStructure($table);

        $updateTableQuery = new SqlAlterTableQuery($table);
        foreach ($this->_objectParameters->getParameters() as $param) {
            $columnName = $param->column ;
            if (!$param->isArray) {
                $type = ObjectParametersDAO::getSqlType(
                    $param,
                    $this->_objectParameters
                );
                $column = new SqlColumn($columnName, $table, $type);

                $parameter = null ;
                if (!array_key_exists($columnName, $tableStructure)) {
                    $parameter = new SqlAddColumn($column);
                } else {
                    $sqlColumnParameters = $tableStructure[$columnName] ;
                    $sqlType = strtoupper(
                        $this->_database->getSqlType(
                            new SqlType($param->sqlType)
                        )
                    );
                    if ($sqlType != strtoupper($sqlColumnParameters->type)
                        || ($param->canBeNull != $sqlColumnParameters->nullable && !$param->primaryKey)
                    ) {
                        $parameter = new SqlChangeColumnType($column);
                    }
                }

                if (!is_null($parameter)) {
                    $updateTableQuery->addParameter($parameter);
                }
            }
        }

        $ok = true ;
        if (count($updateTableQuery->getParameters())>0) {
            $ok = $this->_database->executeQuery($updateTableQuery);
        }
        return $ok ;
    }

    public function createTable()
    {
        $ok = true ;
        $query = ObjectParametersDAO::getObjectParametersAsSqlQuery(
            $this->_objectParameters
        );
        $table = null ;
        if (is_array($query)) {
            $table = $query[0]->getTable();
        } else {
            $table = $query->getTable();
        }

        if (!$this->_database->tableExists(
            $table,
            $this->_objectParameters->isView()
        )
        ) {
            if (is_array($query)) {
                if ($this->_database->supportsInsteadTriggers()) {
                    foreach ($query as $q) {
                        $ok = $this->_database->executeQuery($q);
                        if (!$ok) {
                            break;
                        }
                    }
                } else {
                    $ok = $this->_database->executeQuery($query[0]);
                }
            } else {
                $ok = $this->_database->executeQuery($query);
            }

            if ($ok) {
                $this->_logger->debug("Table created !");
            } else {
                $this->_logger->debug("Error during table cration !");
            }
        } else if (!$this->_objectParameters->isView()) {
            $this->_logger->debug("Updating table !");
            $this->updateTable();
        } else {
            $this->_logger->debug("Table already exists !");
        }

        return $ok;
    }

    public function deleteTable()
    {
        $ok = true ;
        $query
            = ObjectParametersDAO::getObjectParametersAsDeleteSqlQuery(
                $this->_objectParameters
            );
        $table = $query->getTable();
        if ($this->_database->tableExists(
            $table,
            $this->_objectParameters->isView()
        )
        ) {
            $ok = $this->_database->executeQuery($query);
            $this->_logger->debug("Table deleted !");
        } else {
            $this->_logger->debug("Table doesn't exist !");
        }

        return $ok ;
    }

    public function getObjectParameters()
    {
        return $this->_objectParameters ;
    }

    protected function removeRetrievedObject(Object $object)
    {
        if (isset($this->_objectsAlreadyRetrieved[$this->getCompositeKey($object->getId())])
        ) {
            unset(
                $this->_objectsAlreadyRetrieved[
                    $this->getCompositeKey($object->getId())
                ]
            );
        }
    }

    protected function getCompositeKey($key)
    {
        if (is_array($key)) {
            $sKey = null ;
            $first = true ;
            foreach ($key as $keyItem) {
                if (!$first) {
                    $sKey .= '-' ;
                } else {
                    $sKey = '' ;
                    $first = false ;
                }
                
                $skeyItem = $keyItem;
                if (is_object($keyItem)) {
                    $skeyItem = $this->getCompositeKey($keyItem->getId());
                }
                
                $sKey .= $skeyItem ;
            }
            $key = $sKey ;
        }
        return self::getRealKey($key);
    }
    
    protected static function getRealKey($key)
    {
        if ($key instanceof \PsiFramework\Classes\Model\Object) {
        	return self::getRealKey($key->getId());
        } else {
            return $key;
        }
    }

    protected function getObjectProxy(Object $object, /*boolean*/ $checkSecurity=true)
    {
        $o = null ;
        if ($object instanceof ObjectProxy) {
            $o = $object ;
        } else {
            $o = new ObjectProxy($object, $this);
        }
        $o->setCheckSecurity($checkSecurity);
        return $o ;
    }

    protected function addRetrievedObject(Object $object, /*boolean*/ $checkSecurity=true)
    {
        if (self::$storeObjects) {
            $key = $this->getCompositeKey($object->getId());
            
//             if (($object instanceof \PsiFramework\Classes\Model\ObjectProxy && $object->getClassName() == 'PsiFramework\Applications\Redaction\Classes\Model\ArticleInfo') || $object instanceof PsiFramework\Applications\Redaction\Classes\Model\ArticleInfo) {
//                 var_dump($object->getId());
//                 var_dump($key);
//                 echo '<br/>';
//             }
            
            if (isNullOrEmptyString($key)) {
                return ;
            }
    
            $this->_logger->debug(
                "Storing retrieved object " . $this->_objectParameters->getClass() .
                " with key '" . $key . "'"
            );
            
            $this->_objectsAlreadyRetrieved[$key] = $this->getObjectProxy($object, $checkSecurity);
        }
    }

    protected function getRetrievedObject(/*mixed*/ $key)
    {
        $object = null ;
        $key = $this->getCompositeKey($key);
        
        if (isset($this->_objectsAlreadyRetrieved[$key])) {
            $object = $this->_objectsAlreadyRetrieved[$key] ;
            $this->_logger->debug(
                $this->_objectParameters->getClass() .
                " with key '" . $key .
                "' retrieved from previous query."
            );
        }
        return $object ;
    }

    public function getPrimaryKeyClause(/*mixed*/ $key, SqlTable $table=null)
    {
        $pk = $this->_objectParameters->getPrimaryKey();

        $andOperation = new SqlOperation(SqlOperation::$and);
        
        $newKey = array();
        $columns = array();
        
        if (!is_array($pk)) {
            $pk = array($pk);
        }
        
        foreach ($pk as $pkItem) {
            $currKey = null;
            
            if (is_array($key)) {
                $currKey = $key[$pkItem->name];
            } else {
                $currKey = $key;
            }
            $a = self::getColumnValueCouple($pkItem, $currKey);
            foreach ($a as $columnName=>$value) {
                $column = new SqlColumn($columnName, $table);
                
            	$operation = new SqlOperation(SqlOperation::$equal);
            	$operation->addParameter($column);
            	$operation->addParameter(new SqlValue($value));
            
            	$andOperation->addParameter($operation);
            }
        }
        
        return $andOperation ;
    }

    protected function getSelectQueryBuilder(
        SqlTable    &$table=null,
        /*boolean*/ $useJoin=false,
        /*string*/  $alias=null
    )
    {
        $queryBuilder = new QueryBuilder();
        $queryBuilder->createSelectQuery();

        if (is_null($table)) {
            $table = new SqlTable(
                $this->_objectParameters->getTable(),
                $this->_objectParameters->getSchema(),
                $alias
            );
        }
        $queryBuilder->setTable($table);

        if ($useJoin) {
            foreach ($this->_objectParameters->getParameters() as $column) {
                $type = $column->type ;
                if (!$column->isArray) {
                    $applicationManager = ApplicationManager::getInstance();
                    $application = $applicationManager->getApplication();

                    $objParams = $application->getObjectParameter($type);
                    if (!is_null($objParams)) {
                        $pk = $objParams->getPrimaryKey();
                        $sqlJoinClause = null ;

                        //TODO gérer si le nom est différent entre les
                        //deux tables et les clés multiples
                        if (count($pk) == 1) {
                            $primaryKey = $pk[0] ;
                            $sqlJoinClause = new SqlUsing();
                            $sqlJoinClause->addIdentifier($primaryKey->column);
                        } else if (count($pk) > 1) {

                        }
                        if (!is_null($sqlJoinClause)) {
                            $sqlTable = new SqlTable(
                                $objParams->getTable(),
                                $objParams->getSchema()
                            );
                            $sqlJoin = new SqlJoin($sqlTable, $sqlJoinClause);
                            $queryBuilder->addJoin($sqlJoin);
                        }
                    }
                }
            }
        }

        return $queryBuilder ;
    }

    public function retrieveByPk(
        /*mixed*/ $key,
        /*bool*/ $forceReloadFromBase=false,
        /*bool*/ $checkSecurity=true
    )
    {

        if (is_null($this->_objectParameters)) {
            throw new \Exception(
                "Impossible to build query : ".
                "The parameters have not been retrieved."
            );
        }

        if ($checkSecurity) {
            $application = ApplicationManager::getInstance()->getApplication();
            $securityManager = SecurityManager::getInstance();
            $objectName = $this->_objectParameters->getName();
            if (!$securityManager->canReadObject($objectName, '', $application)) {
                throw new SecurityException(
                    "User can't read the Object '".$objectName."' !"
                );
            }
        }

        if (!$forceReloadFromBase) {
            $alreadyRetrievedObject = $this->getRetrievedObject($key);
            if (!is_null($alreadyRetrievedObject)) {
                return $alreadyRetrievedObject ;
            }
        }

        $obj = null ;
        $table = null ;
        $queryBuilder = $this->getSelectQueryBuilder(
            $table,
            $this->_objectParameters->getUseJoin()
        );

        $operation = $this->getPrimaryKeyClause($key, $table);
        if (count($operation->getParameters()) == 0) {
            throw new \Exception("Bad query construction !");
        }

        $queryBuilder->addWhereClause($operation);
        $result = $this->_database->executeQuery($queryBuilder->buildQuery());

        if ($result !== false) {
            $sqlObj = $this->_database->getNextResult($result);
            if ($sqlObj) {
                $obj = $this->getObjectFromSqlObject($sqlObj, true, false, $checkSecurity);
            }
        }

        return $obj;
    }

    protected function processQueryBuilder(QueryBuilder $queryBuilder, /*bool*/ $checkSecurity=true)
    {
        $objects = array();
        $result = $this->_database->executeQuery($queryBuilder->buildQuery());
        if ($result) {
            while (($obj = $this->_database->getNextResult($result)) != null) {
                $object = $this->getObjectFromSqlObject($obj, false, false, $checkSecurity);
                if (!is_null($object)) {
                    $objects[] = $object;
                }
            }
        }
        return $objects;
    }

    public function count()
    {
        $paramName = 'id';
        
        $pk = $this->_objectParameters->getPrimaryKey();
        if (!is_array($pk)) {
            $paramName = $pk->name;
        } else if (count($pk)==1) {
            $paramName = $pk[0]->name;
        }
        
        $queryBuilder = new QueryBuilder();
        $queryBuilder->createSelectQuery();

        $table = new SqlTable(
            $this->_objectParameters->getTable(),
            $this->_objectParameters->getSchema()
        );
        $queryBuilder->setTable($table);

        $idColumn = new SqlColumn(
            $this->_objectParameters->getParameter($paramName)->column,
            $table
        );
        $distinct = new SqlFunction('COUNT');
        $distinct->addParameter($idColumn);
        $distinct->setAlias('count');

        $queryBuilder->addSelectColumn($distinct);

        $result = $this->_database->executeQuery($queryBuilder->buildQuery());
        if ($result) {
            $obj = $this->_database->getNextResult($result);
            return $obj->count ;
        } else {
            return 0 ;
        }
    }

    public function retrieveAll(
        QueryFilter $filter=null,
        Paginator   $paginator=null,
        /*bool*/    $checkSecurity=true
    )
    {

        if (is_null($this->_objectParameters)) {
            throw new \Exception(
                "Impossible to build query : ".
                "The parameters have not been retrieved for".$this->_alias."."
            );
        }

        if ($checkSecurity) {
            $application = ApplicationManager::getInstance()->getApplication();
            $securityManager = SecurityManager::getInstance();
            $objectName = $this->_objectParameters->getName();
            if (!$securityManager->canReadObject(
                $objectName,
                '',
                $application
            )
            ) {
                throw new SecurityException(
                    "User can't read the Object '".$objectName."' !"
                );
            }
        }

        $objects = array();
        $table = null ;
        $queryBuilder = $this->getSelectQueryBuilder(
            $table,
            $this->_objectParameters->getUseJoin()
        );

        if (!is_null($filter) && !is_null($filter->getOperation())) {
            $queryBuilder->addWhereClause($filter->getOperation());
        }

        if (is_null($paginator)) {
            $paginator = new Paginator();
        }
        
        $idColumn = $this->_objectParameters->getParameter('id');
        if (!is_null($idColumn)) {
            $paginator->addOrder(new SqlOrder(new SqlColumn($idColumn->column)), false);
        }
        
        $queryBuilder->addPagination($paginator);
        

        return $this->processQueryBuilder($queryBuilder, $checkSecurity);
    }

    /**
     * Returns the column / value couple
     * 
     * @param Object              $object    The object
     * @param AttributeParameters $parameter The parameter
     * @param SqlTable            $table     The table
     * 
     * @return array
     */
    protected static function getColumnValueCouple(AttributeParameters $parameter, /*value*/ $value=null)
    {
        $coupleArray = array();
        $columnName = $parameter->column;
        
        if (!isNullOrEmptyString($columnName)) {
        
        	$sqlValue = null;
        	if (!is_null($value)) {
        		if ($value instanceof Object) {
        			$value = $value->getId();
        		}
        		$sqlValue = $value;
        	}
        
        	$coupleArray[$columnName] = $sqlValue;
        } else if (!$parameter->isPrimitive() && !$parameter->isPrimitiveObject()) {
    		$applicationManager = ApplicationManager::getInstance();
    		$application = $applicationManager->getApplication();
    		$otherObjectParameters = $application->getObjectParameter($parameter->type);
    		$opk = $otherObjectParameters->getPrimaryKey();
    
    		if (is_array($opk)) {
    			foreach ($opk as $ok) {
    				$columnName = $ok->column;
    				$getter = $ok->getter ;
    				$val = null;
    				if (!is_null($value)) {
    				    $val = $value->$getter();
    				}
				    $innerCoupleArray = self::getColumnValueCouple($ok, $val);
				    $coupleArray = array_merge($coupleArray, $innerCoupleArray);
    			}
    		}
        }
        
        return $coupleArray;
    }
    
    public function save(
        Object   &$object,
        /*bool*/ $performTransaction=true,
        /*bool*/ $checkSecurity=true
    )
    {
        
        if ($checkSecurity) {
            $application = ApplicationManager::getInstance()->getApplication();
            $securityManager = SecurityManager::getInstance();
            $objectName = $this->_objectParameters->getName();
            if (!$securityManager->canWriteObject(
                $objectName,
                '',
                $application
            )
            ) {
                throw new SecurityException(
                    "User can't read the Object '".$objectName."' !"
                );
            }
        }

        if ($performTransaction) {
            $this->_database->startTransaction();
        }

        if ($this->_objectParameters->isView()
            && !$this->_database->supportsInsteadTriggers()
        ) {

            $pkValue = $object->getId();

            $type = SqlQuery::$insert ;
            if (!is_null($pkValue)) {
                $view = $this->retrieveByPk($pkValue);
                if (!is_null($view)) {
                    $type = SqlQuery::$update ;
                }
            }

            $triggers = $this->_objectParameters->getTriggers($type);
            if (!is_null($triggers)) {
                foreach ($triggers as $trigger) {
                    $sqlQuery = ObjectParametersDAO::getSqlQueryFromOperation(
                        $trigger->getOperation(),
                        $this->_objectParameters,
                        $object
                    );
                    $result = $this->_database->executeQuery($sqlQuery);
                    if (!$result) {
                        break;
                    }
                }
            }
        } else {
            $queryBuilder = new QueryBuilder();

            $table = new SqlTable(
                $this->_objectParameters->getTable(),
                $this->_objectParameters->getSchema()
            );
            $queryBuilder->setTable($table);
            $pkValue = $object->getId();
            $insert = true ;

            if (is_null($pkValue)) {
                $queryBuilder->createInsertQuery();
                $insert = true ;
            } else if (is_null($this->retrieveByPk($pkValue, false, $checkSecurity))) {
                $queryBuilder->createInsertQuery();
                $insert = true ;
            } else {
                if ($this->onlyPk()) {
                    return $object->getId();
                }
                $insert = false ;
                $queryBuilder->createUpdateQuery();
                $operation = $this->getPrimaryKeyClause($pkValue, $table);
                if (count($operation->getParameters()) == 0) {
                    throw new \Exception("Bad query construction !");
                }
                $queryBuilder->addWhereClause($operation);
            }

            foreach ($this->_objectParameters->getParameters() as $parameter) {
                if (!$parameter->isArray) {
                    if (!$parameter->primaryKey
                        || ($insert && !is_null($pkValue))
                    ) {
                        $getter = $parameter->getter;
                        $columnArray = self::getColumnValueCouple($parameter, $object->$getter());
                        foreach ($columnArray as $column=>$value) {
                            $queryBuilder->addColumnValue(new SqlColumn($column, $table), new SqlValue($value));
                        }
                    }
                }
            }

            $result = $this->_database->executeQuery(
                $queryBuilder->buildQuery()
            );
            
            if ($result && is_null($pkValue)) {
                $sequence = '' ;
                if (!is_null($this->_objectParameters->getSchema())) {
                    $sequence = $this->_objectParameters->getSchema() . '.' ;
                }
                $pk = $this->_objectParameters->getPrimaryKey();
                $sequence .= $pk[0]->sequence ;
                $newId = $this->_database->getLastId($sequence);
                if (!isNullOrEmptyString($newId)) {
                    $object->setId($newId);
                }
            }
        }


        if ($result) {
            $this->addRetrievedObject($object, $checkSecurity);
        }
        
        if ($performTransaction) {
            if ($result) {
                $this->_database->commit();
            } else {
                $this->_database->rollback();
            }
        }

        return $object->getId();
    }

    /**
     * Delete
     * 
     * @see PsiFramework\Classes\DAO\DAOInterface.IDAO::delete()
     * 
     * @return boolean
     */
    public function delete(
        Object   &$object,
        /*bool*/ $performTransaction=true,
        /*bool*/ $checkSecurity=true
    )
    {

        if ($checkSecurity) {
            $application = ApplicationManager::getInstance()->getApplication();
            $securityManager = SecurityManager::getInstance();
            $objectName = $this->_objectParameters->getName();
            if (!$securityManager->canWriteObject(
                $objectName,
                '',
                $application
            )
            ) {
                throw new SecurityException(
                    "User can't read the Object '".$objectName."' !"
                );
            }
        }

        if ($performTransaction) {
            $this->_database->startTransaction();
        }

        if ($this->_objectParameters->isView()
            && !$this->_database->supportsInsteadTriggers()
        ) {

            $triggers
                = $this->_objectParameters->getTriggers(SqlQuery::$delete);

            if (!is_null($triggers)) {
                foreach ($triggers as $trigger) {
                    $sqlQuery = ObjectParametersDAO::getSqlQueryFromOperation(
                        $trigger->getOperation(),
                        $this->_objectParameters,
                        $object
                    );
                    $result = $this->_database->executeQuery($sqlQuery);
                    if (!$result) {
                        break;
                    }
                }
            }
        } else {
            $queryBuilder = new QueryBuilder();
            $queryBuilder->createDeleteQuery();

            $table = new SqlTable(
                $this->_objectParameters->getTable(),
                $this->_objectParameters->getSchema()
            );
            $queryBuilder->setTable($table);

            $pkValue = $object->getId();
            if (is_null($pkValue)) {
                throw new \Exception("Object has no primary key");
            }

            $operation = $this->getPrimaryKeyClause($pkValue, $table);
            if (count($operation->getParameters()) == 0) {
                throw new \Exception("Bad query construction !");
            }
            $queryBuilder->addWhereClause($operation);
            $result = $this->_database->executeQuery(
                $queryBuilder->buildQuery()
            );

        }

        if ($result) {
            $this->removeRetrievedObject($object);
            $object = null ;
        }
        if ($performTransaction) {
            if ($result) {
                $this->_database->commit();
            } else {
                $this->_database->rollback();
            }
        }

        return $result ;
    }

    protected function castValue(/*mixed*/ $value, /*string*/ $type)
    {
        if (is_null($value)) {
            return null ;
        }

        $castedValue = $value ;

        switch(strtoupper($type)) {
        case "STRING" :
            $castedValue = (string) $value ;
            break;
        case "BOOL" :
        case "BOOLEAN" :
            if (isNullOrEmptyString($value)) {
                $castedValue = null ;
            } else {
                $castedValue = $this->_database->getBooleanValue($value);
            }
            break;
        case "FLOAT" :
            $castedValue = (float) $value ;
            break;
        case "INT" :
        case "INTEGER" :
            $castedValue = (int) $value ;
            break;
        case "DOUBLE" :
        	$castedValue = (double) $value ;
        	break;
        }

        return $castedValue ;
    }

    public function getObjectFromSqlObject(
        /*object*/ $sqlObject,
        /*boolean*/ $forceReloadFromBase=false,
        /*boolean*/ $useJoin=true,
        /*boolean*/ $checkSecurity=true
    )
    {
        
        $pk = $this->_objectParameters->getPrimaryKey();
        if (!$forceReloadFromBase) {
            if (!is_null($pk)) {
                $key = '' ;
                if (!is_array($pk)) {
                    $pk = array($pk);
                }
                
                foreach ($pk as $k) {
                    $a = self::getColumnValueCouple($k);
                    foreach ($a as $columnName=>$v) {
                        if (!isNullOrEmptyString($key)) {
                        	$key .= '-' ;
                        }
                        
                        $key .= $sqlObject->$columnName;
                        
                    }
                }
                
                if (!isNullOrEmptyString($key)) {
                    $this->_logger->debug(
                        "Trying to retrieve object with key : " .
                        $key . " from " . $columnName
                    );

                    $alreadyRetrievedObject = $this->getRetrievedObject($key);
                    if (!is_null($alreadyRetrievedObject)) {
                        return $alreadyRetrievedObject ;
                    }
                }
            }
        }
        
        if (is_null($this->_objectParameters)) {
            throw new \Exception(
                'Impossible to build object : '.
                'The parameters have not been retrieved.'
            );
        }

        $obj = ObjectFactory::getNewObject($this->_objectParameters->getName());

        $foreignList = array();
        $pkValue = null;
        if (!is_null($pk) && is_array($pk) && count($pk)>1) {
            $pkValue = array();
        }

        foreach ($this->_objectParameters->getParameters() as $column) {
            $columnName = $column->column ;
            if ((!isNullOrEmptyString($columnName) && property_exists($sqlObject, $columnName))
                || (!$column->isPrimitive() && !$column->isPrimitiveObject())
            ) { 
                $columnValue = null;
                
                if (!empty($sqlObject->$columnName)) {
                    $columnValue = $sqlObject->$columnName ;
                }
                
                if ($column->primaryKey) {
                    if (is_array($pkValue)) {
                        $pkValue[$column->name] = $columnValue ;
                    } else {
                        $pkValue = $columnValue ;
                    }
                }
    
                $setter = $column->setter ;
                $type = $column->type ;
                
                if ($column->isPrimitive()) {
                    $obj->$setter($this->castValue($columnValue, $type));
                } else if ($column->isPrimitiveObject()) {
                    $primitiveObject = null ;
                    if (!isNullOrEmptyString($columnValue)) {
                        $primitiveObject
                            = ObjectManager::getInstance()->getPrimitiveObjectInstance(
                                $column->type
                            );
                        $primitiveObject->unserialize($columnValue);
                    }
                    $obj->$setter($primitiveObject);
                } else { //if it's an object
                    $this->addRetrievedObject($obj, $checkSecurity);
                    $obj = $this->getObjectProxy($obj, $checkSecurity);
    
                    if (!$column->lazy) {
    
                        $dao = DAOFactory::getDAO($type);
                        if (!is_null($dao)) {
                            if ($column->isArray) {
                                $foreignList[] = $column ;
                            } else {
                                $foreignObject = null ;
                                if ($useJoin
                                    && $this->_objectParameters->getUseJoin()
                                    && $type != $this->_objectParameters->getClass()
                                ) {
                                    $foreignObject = $dao->getObjectFromSqlObject(
                                        $sqlObject, false, false, $checkSecurity
                                    );
                                }
                                if (is_null($foreignObject) && !is_null($columnValue)) {
                                    $foreignObject = $dao->retrieveByPk(
                                        $columnValue, false, $checkSecurity
                                    );
                                }
                                $obj->$setter($foreignObject);
                            }
                        }
                    } else {
                        
                        if (is_null($columnValue)
                            && $column->isArray
                        ) {
                            $applicationManager = ApplicationManager::getInstance();
                            $application = $applicationManager->getApplication();
                            $otherObjectParameters = $application->getObjectParameter($column->type);
                            $opk = $otherObjectParameters->getPrimaryKey();
                            if (!is_null($opk) && is_array($opk)) {
                                if (count($opk)>1) {
                                    $columnValue = $obj->getId();
                                } else if (count($opk)==1 && $opk[0]->type == $this->_objectParameters->getName()) {
                                    $columnValue = $obj;
                                }
                            }
                        }
                        
                        if (!is_null($columnValue)) {
                            $lazySetter = 'lazy'.$setter ;
                            $obj->$lazySetter($columnValue);
                            $obj->$setter(null);
                        }
                    }
                }
            } else {
                $this->_logger->debug('The variable "'.$column->name.'" for "'.$this->_objectParameters->getName().'" couldn\'t be loaded !');
            }
        }
        
        $this->addRetrievedObject($obj, $checkSecurity);

        if (count($foreignList) > 0 && !is_null($pkValue)) {
            foreach ($foreignList as $column) {
                $this->addLinkedObject($obj, $column, $pkValue, $checkSecurity);
            }
        }

        return $this->getRetrievedObject($obj->getId());
    }

    public function addLinkedObject(
        ObjectProxy &$obj,
        AttributeParameters $column,
        $id,
        /*boolean*/ $checkSecurity=true
    )
    {

        $setter = $column->setter ;

        if (!isNullOrEmptyString($id)) {

            $type = $column->type ;
            $dao = DAOFactory::getDAO($type);

            if (!is_null($dao)) {
            	$foreignObject = null ;
                if ($column->isArray) {
                    $filter = new QueryFilter();
                    if (!$id instanceof Object && !is_array($id)) {
                        $operation = new SqlOperation(SqlOperation::$equal);
                        $operation->addParameter(
                            new SqlColumn($column->foreignColumn)
                        );
                        $operation->addParameter(new SqlValue($id));
                        $filter->setOperation($operation);
                    } else {
                        $filter->setOperation($this->getPrimaryKeyClause($id));
                    }
                    $foreignObject = $dao->retrieveAll($filter, null, $checkSecurity);
                } else {
                    $foreignObject = $dao->retrieveByPk($id, false, $checkSecurity);
                }
                $obj->{$setter}($foreignObject);

                $this->addRetrievedObject($obj, $checkSecurity);
                $obj->setRetrieved($column->name);
            }
        } else {
            $obj->$setter(null);
            $obj->setRetrieved($column->name);
        }
    }
    
    public function prepareForCopy(Object &$object)
    {
        
    }
    
    public function getNoCopyParametersList()
    {
        return array();
    }
    
    public function refresh(Object &$object)
    {
        if (!isNullOrEmptyString($object->getId())) {
           $obj =  $this->retrieveByPk($object->getId(), true);
           $object = $obj;
        }
    }
}

